/*
	Copyright (C) 2010 Salil Bhagurkar

	This file is part of k3

	k3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	k3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with k3.  If not, see <http://www.gnu.org/licenses/>.
*/

/*Miscellaneous architecture specific code*/



#include <shared.h>


/* Bios call zone. This enables us to make 16 bit real mode interrupt calls when required
 * to detect stuff or to do video mode setting or to read write from disks without proper
 * drivers.
 */

//Backup value of the timer configuration to make bios calls
static unsigned long pm_hz = 18;

/* This file includes the real mode manager code which is 16 bit assembled. As the os boots
 * this code is loaded at IRMM_DEST_ADDR (destination address) and then called by passing
 * registers that need to be passed to the interrupt in defined locations. The code handles
 * switching the processor to real mode and back to protected mode.
 */
#include "irmm.h"

/**
	This function is called when null pointer execution takes place
	For this, a jump is placed at zero memory location
*/
extern void *get_stack_address();
void arch_null_execution_trap()
{
	u32 *stack = get_stack_address();
	printk("Fatal: Null pointer was executed!\n");
	printk("Stack = %x\n", stack);
	while(1)
		arch_halt();
}

extern void *null_trap;
void arch_init_null_execution_trap()
{
	memcpy(0, null_trap, 6);
}

static void arch_setclock(unsigned long hz);
/*Restore protected mode by reloading the required GDT and IDT and then restoring IRQ
 * configuration, followed by the clock.
 */
int arch_restore_pm()
{
	int err=0;
	_reload_gdt();
	_reload_idt();
	err=restore_irq_pm();
	arch_setclock(pm_hz);
	return err;
}

/*Prepare the configuration for real mode*/
int arch_prepare_rm()
{
	int err=0;
	err=prepare_irq_rm();
	if(err)
		return err;
	arch_setclock(18); /*Set to the real mode ticking freq*/
	return err;
}

/*This is where the register information is passed to the real mode manager. It is passed
 * in the struct regs sequence.
 */
#define RM_REG_MEM 0x500

extern void callbios();

/*This will execute a bios interrupt in 16 bit real mode by calling the real mode manager.
 * This finally calls the callbios() assembly function which will call the real mode manager
 * The call to callbios() is surrounded by preparation and restoration of real mode condition and
 * pmode condition respectively.
 */
int arch_bios_int(struct regs *regs)
{
	int err=0;
	if(!regs)
		return EPINV;
	if(regs->f>0xff)
		return EPINV;
	memcpy((void *)RM_REG_MEM,regs,sizeof(struct regs));
	arch_block_interrupts();
	arch_prepare_rm();
	callbios(); /*asm function that jumps to 0x600 where the biosi was loaded*/
	arch_restore_pm();
	arch_allow_interrupts();
	memcpy(regs,(void *)RM_REG_MEM,sizeof(struct regs));
	return 0;
}


/*Copy the real mode manager to < 1 mb address so that it can execute*/
int arch_init_irmm()
{
	struct regs regs;
	dprint(D_PROG, "bios call", "Initialize");
	memcpy((void *)IRMM_DEST_ADDR, irmm_bin, 512); //transfer irmm
	regs.f = 0x11;
	arch_bios_int(&regs);
	dprint(D_INFO, "bios call", "Verified");
	return 0;
}




/*Prepare a task before it is sitched to. This puts the start address and the arguments to the
 * stack and stores the new stack pointer.
 * Called from sched.c
 */
void *arch_prepare_task(void *stackptr, void *fs_node, void *entry_fn)
{
	(stackptr)-=sizeof(void *);
	*((void **)(stackptr)) = fs_node;
	(stackptr)-=sizeof(void *);
	*((void **)(stackptr)) = entry_fn;
	return stackptr;
}

/*This is called when a task returns from it's function. It comes back to the assembly
 * routine which jumped to it, which then calls this function once the
 */
void arch_end_of_task()
{
	printk("Task ended (%s)\n", current->fs->name);
}

//Set the PIT
static void arch_setclock(unsigned long hz)
{
	u16 divisor = 1193182 / hz;
	outb(0x43, 0x36);
	outb(0x40, (u8)(divisor));
	outb(0x40, (u8)(divisor>>8));
}

void arch_configure_timer(int (*timer)(void *data),unsigned long hz)
{
	arch_setclock(hz);
	pm_hz = hz;
	arch_configure_interrupt_handler(0, timer, NULL);
}

void arch_remove_timer()
{
	arch_remove_interrupt_handler(0);
}

/*ms and us delay*/


#define NR_CALIB 5
#define CONFIG_DEFCALIB 1000
#define SYS_HZ 1000

volatile int clock_flag=1;
static int arch_clock_handler()
{
	clock_flag=0;
	return 0;
}

u32 calibs[NR_CALIB];
u32 calib=CONFIG_DEFCALIB;

int arch_delay_ms(u32 x)
{
	u32 c,ci;
	ci=(calib * SYS_HZ) / 1000;
	while(x-- > 0) {
		c=ci;
		while(c-- > 0) {
			clock_flag=0; //Emulate the calibration environment
		}
	}
	return 0;
}

int arch_delay_us(u32 x)
{
	u32 c,ci;
	ci=(calib * SYS_HZ) / 1000000;
	while(x-- > 0) {
		c=ci;
		while(c-- > 0) {
			clock_flag=0; //Emulate the calibration environment
		}
	}
	return 0;
}


static int arch_make_calibs()
{
	u32 i,c;
	clock_flag=1;
	//Synchronize with the clock first
	while(clock_flag) {
		c++;
	}
	for(i=0;i<NR_CALIB;i++) {
		c=0;
		clock_flag=1;
		while(clock_flag) {
			c++;
		}
		calibs[i]=c;
	}
	calib=0;
	for(i=0;i<NR_CALIB;i++) {
		calib+=(calibs[i]/NR_CALIB);
	}
}

int arch_init_delay_calibration()
{
	dprint(D_PROG, "delay", "Calibrating");
	arch_configure_timer(arch_clock_handler, 1000);
	arch_make_calibs();
	arch_make_calibs();
	arch_remove_timer();
	dprint(D_INFO, "delay", "Calibration = %u", calib);
	return 0;
}


static volatile int cnt = 0;
static int clock_handler_2()
{
	cnt++;
	return 0;
}


static inline void arch_read_time_stamp( u32 *low, u32 *high ) {
    asm volatile( "rdtsc" : "=a" ( *low ), "=d" ( *high ) );
}

void arch_init_cpu_speed_tsc()
{
	dprint(D_PROG, "cpu speed", "Calculating");
	u32 i, j, k;
	arch_configure_timer(clock_handler_2, 1000);
	arch_read_time_stamp(&i, &k);
	cnt = 0;
	while(cnt < 50);
	arch_read_time_stamp(&j, &k);
	arch_remove_timer();
	k = j - i;
	k *= 20;
	dprint(D_INFO, "cpu speed", "%u", k);
}

void arch_register_memory()
{
	malloc_add_reserved_zone((void *)arch_kernel_start, (void *)arch_kernel_end, "kernel");
	malloc_add_reserved_zone((void *)IRMM_DEST_ADDR, (void *)(IRMM_DEST_ADDR + irmm_bin_len), "irmm");
	malloc_add_reserved_zone((void *)RM_REG_MEM, (void *)(RM_REG_MEM + 512), "rmregs");
}

int arch_init()
{
	init_printk();

	dprint(D_PROG, "arch", "Initialize");

	arch_init_null_execution_trap();
	arch_init_irmm();
	arch_info();
	init_irq();
	arch_init_delay_calibration();

	/*struct regs regs;
	memset(&regs, 0, sizeof(struct regs));
	regs.f = 0x19;
	arch_bios_int(&regs);*/

	arch_init_cpu_speed_tsc();
	return 0;
}
